package xmlparser;

import java.util.HashMap;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

//import wifi_geo_algo.client.objects ;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;

import objects.Client;
import objects.Measurement;
import objects.Point;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.SAXException;

import divTools.CheckMAC;

public class XmlParser {

	private static String valid_xsd_scheme="wifigeo_lib/received.xsd" ; ////home/tienne/Documents/etudes/UTBM/TX52/wifi-geolocalisation/source/wifi_geo_server/src/xmlparser/received.xsd"; //

	/**
	 * Check the integrity of the received XML File
	 * @param instream
	 * @throws SAXException
	 * @throws IOException
	 */
	public static void XmlValidator(InputStream instream) throws SAXException, IOException{
	        // define the type of schema - we use W3C:
	        String schemaLang = "http://www.w3.org/2001/XMLSchema";

	        // get validation driver:
	        SchemaFactory factory = SchemaFactory.newInstance(schemaLang);

	        // create schema by reading it from an XSD file:
	        Schema schema = factory.newSchema(new StreamSource(valid_xsd_scheme));
	        Validator validator = schema.newValidator();

	        // at last perform validation:
	        validator.validate(new StreamSource(instream));
	}
	
    /**
     * Document Builder Factory
     * @return DocumentBuilder
     */
    public static DocumentBuilder DocBuilder(){

        //new factory instance
        DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance() ;
        //Using factory get an instance of document builder
        DocumentBuilder dBuilder = null;
        try {
            dBuilder = dbFactory.newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
            System.out.println("Error when building the DocumentBuilder : " + e);
        }
        return dBuilder;
    }

    /**
     * Convert Document to InputStream
     * @param Document doc to convert
     * @return inputStream
     */
    public static byte[] DocToBuffer(Document doc){

        //Create transformer instance
        TransformerFactory transFact = TransformerFactory.newInstance();
        Transformer transform = null;
        try {
            transform = transFact.newTransformer();
        } catch (TransformerConfigurationException e) {
            e.printStackTrace();
        }
        //Edit transformer properties

        ///Indent file
        transform.setOutputProperty(OutputKeys.INDENT, "yes");
        //Set as XML method
        transform.setOutputProperty(OutputKeys.METHOD,"xml");

        // create byte to construct a string. 
        StringWriter sw = new StringWriter();
        //Store Transform result in
        StreamResult result = new StreamResult(sw);
        //Fill Transform source
        DOMSource source = new DOMSource(doc);
        try {
            transform.transform(source, result);
        } catch (TransformerException e) {
            e.printStackTrace();
            System.out.println("Cannot convert result in order to send it !!");
        }

        System.out.println("Here the file :\n" +sw.toString());
        
        //Convert StringWritter to Byte
        byte[] tobyte = sw.toString().getBytes();

        return tobyte;
    }



    /**
     * Convert buffer to Document
     * @param Socket received byte[] Buffer
     * @return Document
     * @throws IOException 
     * @throws SAXException 
     */
    public static Document BufferToDoc(byte[] buffer) throws SAXException, IOException{

        //change the buffer to a stream
        InputStream instream = new ByteArrayInputStream(buffer);
        
//        try {
			XmlValidator(instream);
//		} catch (SAXException e) {
//            System.out.println("The received file is not valid. Please check the sent xml.");
//			e.printStackTrace();
//		} catch (IOException e) {
//			e.printStackTrace();
//		}
		InputStream instream2 = new ByteArrayInputStream(buffer);

        //Parse the byte array
        Document doc = null;
        try {
            doc = DocBuilder().parse(instream2);
        } catch (Exception e) {
            System.out.println("Cannot parse the received file.");
            e.printStackTrace();
        }
        return doc;
    }

    /**
     * Return tag value from a tag name
     * @param String tag name
     * @param Element e where to extract the value 
     * @return String tag value
     */
    private static String getTagValue(String sTag, Element eElement){
        NodeList nlList= eElement.getElementsByTagName(sTag).item(0).getChildNodes();
        Node nValue = (Node) nlList.item(0); 

        return nValue.getNodeValue();    
    }

    
    /**
     * Import from XML file and convert to Byte array, importFromXmlToByte 
     * @param String PathName
     * @return byte[]
     */
    public static byte [] importFromXmlToByte(String pathName){

        //Import file
        File fileXml = new File (pathName) ;

        //Get file size
        byte[] buffer = new byte[(int) fileXml.length()] ;

        //Convert File to InputStream
        InputStream xmlFileStream = null ;
        try {
            xmlFileStream = new FileInputStream (fileXml);;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            System.out.println("Cannot convert File to inputStream, file " + fileXml.getAbsolutePath() + " not found");
        }

        try {
            System.out.println("Will send " + xmlFileStream.available() + " bytes...");
            xmlFileStream.read(buffer) ;
        } catch (IOException e) {
            e.printStackTrace();
        }

        //System.out.println("The file has " + getBytesFromFile(fileXml) + " bytes");
        System.out.println("The buffer " + buffer.length + " bytes");

        return buffer;
    }

    
    /**
     * Convert received XML byte to Object
     * @param byte[]
     * @param ObjectFromAp to fill
     * @return ObjectFromAp
     */
    public static void convertReceivedXmlBytesToObject ( byte[] buffer, Client client ) throws Exception{

        //Get filled Document object
        Document doc = BufferToDoc(buffer);

        System.out.println("Racine :" + doc.getDocumentElement().getNodeName());
        System.out.println("-----------------------");

        //Get the list of the root node
        NodeList clientDef= doc.getElementsByTagName("client");

        for (int i = 0; i < clientDef.getLength(); i ++) {

            Node nNode = clientDef.item(i);
            if ( nNode.getNodeType() == Node.ELEMENT_NODE ) {  // ELEMENT_NODE = 1

                //Get Element of the Node
                Element eElement = (Element) nNode;
                String ap_mac = getTagValue("ap_mac",eElement);
                String cli_mac = getTagValue("cli_mac",eElement);
                if (CheckMAC.IsMAC(ap_mac) && CheckMAC.IsMAC(cli_mac) ){
                	client.setApMac(ap_mac.toUpperCase());
                	client.setCliMac(cli_mac.toUpperCase()) ;
                }
                else{
                	throw new Exception("Invalid mac Address.");
                }
                String algorithm = getTagValue("algorithm",eElement);
                
                //Set object attributes 
                
                client.setAntennaGain(Integer.parseInt(getTagValue("antennagain",eElement)));
                client.setAlgorithm(algorithm);		 

                System.out.println("ap_mac : " + client.getApMac());
                System.out.println("cli_mac : " + client.getCliMac());
                System.out.println("antennaGain : " + client.getAntennaGain());
                System.out.println("algorithm : " + client.getAlgorithm());
            }
        }

        //NodeList nodMesures = doc.getElementsByTagName("request");
        NodeList nodMesures = doc.getElementsByTagName("ap");

        HashMap<String, Measurement> receivedMesures = new HashMap<String, Measurement>();
        
        Measurement measurement = new Measurement();
        
        System.out.println(nodMesures.getLength());
        for (int i = 0; i < nodMesures.getLength(); i ++) {

            Node nNode = nodMesures.item(i);
            if ( nNode.getNodeType() == Node.ELEMENT_NODE ) {  // ELEMENT_NODE = 1
                //we fill all <ap> into the arraylist<ap>
                Element eElement = (Element) nNode;
                String mac = getTagValue("mac",eElement) ;

                
                //Fill received xml attributes into measurement object 
                if (CheckMAC.IsMAC(mac)  ){
                	measurement.setMacAdress(mac);
                }
                else{
                	throw new Exception("Invalid mac Address.");
                }
                String rxpower = getTagValue("rxpower",eElement)  ;
                measurement.setPowerValue(Float.parseFloat(rxpower));
                
                System.out.println("Mac : " + mac);
                System.out.println("rxpower : " + rxpower);
            }
            receivedMesures.put(measurement.getMacAdress(), measurement);
        }
        client.setMeasurements(receivedMesures);
    }


    /**
     * Write received Byte to XML file
     * @param byte[]
     * @param String Filename
     */
    public static void byteToWriteXmlFile(byte data[], String fileName) {
        try {
            FileOutputStream out = new FileOutputStream(new File(fileName));
            out.write(data);
            out.close();
        } catch (IOException e) {
            System.out.println("Cannot write to file " + fileName + ", reason : " + e);
        }	
    }

    /**
     * Convert document to Byte
     * @param Document 
     * @param String Filename
     * @return byte[]
     */
    public static byte[] docToBytes(Document doc, String filename) {
        byte[] buffer = null;
        try {
            // Prepare the DOM document for writing
            Source source = new DOMSource(doc);

            // Prepare the output file
            File file = new File(filename);
            StreamResult result = new StreamResult(file);

            // Write the DOM document to the file
            Transformer xformer = TransformerFactory.newInstance().newTransformer();

            //transform 
            xformer.transform(source, result);

            OutputStream in = result.getOutputStream();
            try {
                in.write(buffer);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        } catch (TransformerConfigurationException e) {
        } catch (TransformerException e) {
        }

        return buffer;
    }

    /**
     * Create DOM statement in XML send format
     * @param Document to 
     * @param ObjectToPhone 
     */
    public static Document convertObjectToDocument(Client tophone ){

        //get empty DOM object
    	Document dom =  XmlParser.DocBuilder().newDocument();

        //create the root element 
        Element rootElement = dom.createElement("result");

        //call the body to append into root element
        Element result = createToPhoneElement(tophone, dom, rootElement);

        //append it to the root
        dom.appendChild(result);
		return dom;
        
    }

    /**
     * Fill the body of the xml sending file
     * @param ObjectToPhone
     * @param Element
     */
    private static Element createToPhoneElement(Client client, Document dom, Element resultElement){

        Element timeElem = dom.createElement("time");
        Text timeText = dom. createTextNode (Long.toString(client.getTimestamp()));
        timeElem.appendChild(timeText);
        resultElement.appendChild(timeElem);

        Element cliMacElem = dom.createElement("cli_mac");
        Text cliMacText = dom.createTextNode (client.getCliMac());
        cliMacElem.appendChild(cliMacText);
        resultElement.appendChild(cliMacElem);

        Element apElement = dom.createElement("coords");

        Element idCarteElem = dom.createElement("id_carte");
        Text IdCarteText = dom. createTextNode (client.getIdCarte());
        idCarteElem.appendChild(IdCarteText);
        apElement.appendChild(idCarteElem);

        Element xElem = dom.createElement("x");
        Text xText = dom.createTextNode(client.getPosition().getX().toString());
        xElem.appendChild(xText);
        apElement.appendChild(xElem);

        Element yElem = dom.createElement("y");
        Text yText = dom.createTextNode(client.getPosition().getY().toString());
        yElem.appendChild(yText);
        apElement.appendChild(yElem);

        Element zElem = dom.createElement("z");
        Text zText = dom.createTextNode(client.getPosition().getZ().toString());
        zElem.appendChild(zText);
        apElement.appendChild(zElem);

        //append to the root
        resultElement.appendChild(apElement);

        return resultElement;

    }
    
    
    
    /**
    *
    * Convert XML bytes to send to Object
    * @param byte[] buffer
    * @param Client tophone
     * @throws IOException 
     * @throws SAXException 
    */
   public static void convertObjectToXml (byte[] buffer, Client tophone) throws SAXException, IOException{
       /*
          <?xml version="1.0"?>
          <result>
          <date>12345</date> 
          <cli_mac>EE:EE:EE:EE:EE:EE</cli_mac>
          <coords>
          <id_carte>4ZA2</id_carte>
          <x>X</x>
          <y>Y</y>
          <z>Z</z>
          </coords>
          </result>
          */
       //File fileXml = new File(sniff) ;

       //Get filled Document object

       Document doc = BufferToDoc(buffer);

       System.out.println("Racine :" + doc.getDocumentElement().getNodeName());
       System.out.println("-----------------------");

       NodeList clientDef= doc.getElementsByTagName("result");
       //date is set 
       for (int i = 0; i < clientDef.getLength(); i ++) {

           Node nNode = clientDef.item(i);
           if ( nNode.getNodeType() == Node.ELEMENT_NODE ) {  // ELEMENT_NODE = 1

               Element eElement = (Element) nNode;
               //xml attributes 
               //date is already set 
               tophone.setCliMac(getTagValue("cli_mac",eElement));

               System.out.println("timestamp : " + tophone.getTimestamp());
               System.out.println("cli_mac : " + tophone.getCliMac());
           }
       }

       NodeList nodCoords = doc.getElementsByTagName("coords");

       for (int i = 0; i < nodCoords.getLength(); i ++) {

           Node nNode = nodCoords.item(i);
           if ( nNode.getNodeType() == Node.ELEMENT_NODE ) {  // ELEMENT_NODE = 1

               Element eElement = (Element) nNode;
               //xml attributes 

               String id_carte = getTagValue("id_carte",eElement) ;
               String x = getTagValue("x",eElement)  ;
               String y = getTagValue("y",eElement)  ;
               String z = getTagValue("z",eElement)  ;

               Point position = new Point();

               //position.setX((int)Float.parseFloat(x));
               position.setX(Float.parseFloat(x));
               position.setY(Float.parseFloat(y));
               position.setZ(Float.parseFloat(z));
               tophone.setIdCarte(id_carte);
               tophone.setPosition(position);
               
               System.out.println("id_ carte : " + tophone.getIdCarte());
               System.out.println("y : " + tophone.getPosition().getX());
               System.out.println("y : " + tophone.getPosition().getY());
               System.out.println("z : " + tophone.getPosition().getZ());
           }
       }
   }
}
